/**
 * @file SdpFactory.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief
 * @version 0.1
 * @date 2024-1-13
 *
 * @copyright Copyright Sci-Tech Laboratories (c) 2024
 *
 */

#include "SdpOptions.h"
#include "SdpFactory.h"
#include <string>
#include <regex>

namespace STL
{
    std::string getCommandStr(GB_SDP_Command cmd)
    {
        switch (cmd)
        {
        case GB_SDP_Command_Playback:
            return "Playback";
        case GB_SDP_Command_Download:
            return "Download";
        case GB_SDP_Command_Talk:
            return "Talk";
        case GB_SDP_Command_Play:
        default:
            return "Play";
        }
    }

    GB_SDP_Command getCommandEnum(std::string cmd)
    {
        if (cmd == "Play")
        {
            return GB_SDP_Command::GB_SDP_Command_Play;
        }
        else if (cmd == "Playback")
        {
            return GB_SDP_Command::GB_SDP_Command_Playback;
        }
        else if (cmd == "Download")
        {
            return GB_SDP_Command::GB_SDP_Command_Download;
        }
        else if (cmd == "Talk")
        {
            return GB_SDP_Command::GB_SDP_Command_Talk;
        }
        else
        {
            return GB_SDP_Command::GB_SDP_Command_None;
        }
    }

    std::string getProtoStr(GB_SDP_Proto proto)
    {
        switch (proto)
        {
        case GB_SDP_Proto_TCP:
            return "TCP/RTP/AVP";
        case GB_SDP_Proto_UDP:
        default:
            return "RTP/AVP";
        }
    }

    GB_SDP_Proto getProtoEnum(std::string proto)
    {
        if (proto == "TCP/RTP/AVP" || proto == "RTP/AVP/TCP")
        {
            return GB_SDP_Proto::GB_SDP_Proto_TCP;
        }
        else if (proto == "RTP/AVP" || proto == "UDP/RTP/AVP" || proto == "RTP/AVP/UDP")
        {
            return GB_SDP_Proto::GB_SDP_Proto_UDP;
        }
        else
        {
            return GB_SDP_Proto::GB_SDP_Proto_None;
        }
    }

    std::string getTcpTypeStr(GB_SDP_TcpType type)
    {
        switch (type)
        {
        case GB_SDP_TcpType_Active:
            return "active";
        case GB_SDP_TcpType_Passive:
        default:
            return "passive";
        }
    }

    GB_SDP_TcpType getTcpTypeEnum(std::string type)
    {
        if (type == "active")
        {
            return GB_SDP_TcpType::GB_SDP_TcpType_Active;
        }
        else if (type == "passive")
        {
            return GB_SDP_TcpType::GB_SDP_TcpType_Passive;
        }
        else
        {
            return GB_SDP_TcpType::GB_SDP_TcpType_None;
        }
    }

    // std::string getIpType(GB_SDP_IpType type)
    // {
    //     switch (type)
    //     {
    //     case GB_SDP_IpType_IP6:
    //         return "IP6";
    //     case GB_SDP_IpType_IP4:
    //     default:
    //         return "IP4";
    //     }
    // }
    // GB_SDP_IpType getIpType(std::string type)
    // {
    //     if (type == "IP6")
    //         return GB_SDP_IpType_IP6;
    //     else
    //         return GB_SDP_IpType_IP4;
    // }

    /**
     * @brief 生成ssrc（详见国标SDP要求）
     *
     * @param cmd 命令值
     * @param deviceId4to8 String 设备编码4到8位 符合正则表达式 ^\d{5}$ [340(20000)002000000002]
     * @return std::string SDP字符串
     */
    std::string genSSRC(GB_SDP_Command cmd, std::string deviceId4to8, std::string deviceId17to20)
    {
        static int staticSsrcIndex = 1000;
        std::string ret = "";
        if (cmd == GB_SDP_Command_Play || cmd == GB_SDP_Command_Talk)
        {
            ret += "0"; // 0是实时
        }
        else
        {
            ret += "1"; // 1是回放
        }

        // if (deviceId4to8.length() == 5)
        if (regex_match(deviceId4to8, std::regex("([0-9]{5})")))
        {
            ret += deviceId4to8;
        }
        else
        {
            ret += "00000";
        }
        if (regex_match(deviceId17to20, std::regex("([0-9]{4})")))
        {
            ret += deviceId17to20;
        }
        else
        {
            if (staticSsrcIndex >= 9999)
                staticSsrcIndex = 1000;
            ret += to_string(staticSsrcIndex++);
        }
        return ret;
    }
    std::string genSSRC(GBSdpOptions &options)
    {
        std::string deviceId4to8 = "00000";
        std::string deviceId17to20 = "";
        if (options.o_username.size() == 20)
        {
            deviceId4to8 = options.o_username.substr(3, 5);
        }
        if (options.deviceId.size() == 20)
        {
            deviceId17to20 = options.deviceId.substr(16, 4);
        }
        std::string ssrc = genSSRC(options.s_command, deviceId4to8, deviceId17to20);
        options.y_ssrc = ssrc;
        return ssrc;
    }
    unsigned int getSsrcFromStr(std::string ssrc)
    {
        return std::stoi(ssrc);
    }

    std::string getSsrcFromUint(unsigned int ssrc)
    {
        std::string ret = to_string(ssrc);
        int insertZeroCount = 10 - ret.size();
        for (int i = 0; i < insertZeroCount; i++)
        {
            ret = std::string("0") + ret;
        }
        return ret;
    }

    int getSdpValue(std::string &sdp, std::string key, std::string &value, int offset /*=0*/)
    {
        if (key.size() != 1)
        {
            return 0;
        }
        size_t endpos = 0;
        size_t pos = sdp.find(key + "=", offset);
        if (pos != std::string::npos)
        {
            pos += 2;
            endpos = sdp.find("\r\n", pos);
            if (endpos != std::string::npos)
            {
                value = sdp.substr(pos, endpos - pos);
            }
            else
            {
                endpos = sdp.find("\n", pos);
                if (endpos != std::string::npos)
                {
                    value = sdp.substr(pos, endpos - pos);
                }
                else
                {
                    value = sdp.substr(pos);
                }
            }
        }
        // printf("[key:%s,pos:%d,end:%d] => [%s]\n", key.c_str(), pos, endpos, value.c_str());
        return endpos;
    }

    std::vector<std::string> split(std::string str, std::string pattern)
    {
        size_t pos;
        std::vector<std::string> result;
        if (str.empty())
            return result;
        str += pattern;
        size_t size = str.size();

        for (size_t i = 0; i < size; i++)
        {
            pos = str.find(pattern, i);
            if (pos < size)
            {
                std::string s = str.substr(i, pos - i);
                result.push_back(s);
                i = pos + pattern.size() - 1;
            }
        }

        return result;
    }

    GBSdpOptions GBSdpFactory::parseSdp(std::string sdp)
    {
        GBSdpOptions ret;
        std::string tempString;
        std::vector<std::string> tempVector;
        int offset = 0;
        offset = getSdpValue(sdp, "v", ret.v_version, offset);
        offset = getSdpValue(sdp, "o", tempString, offset);
        tempVector = split(tempString, " ");
        for (size_t i = 0; i < tempVector.size(); i++)
        {
            switch (i)
            {
            case 0:
                ret.o_username = tempVector.at(i);
                break;
            case 1:
                ret.o_sess_id = tempVector.at(i);
                break;
            case 2:
                ret.o_sess_version = tempVector.at(i);
                break;
            case 3:
                ret.o_net_type = tempVector.at(i);
                break;
            case 4:
                ret.o_addr_type = tempVector.at(i);
                break;
            case 5:
                ret.o_unicast_address = tempVector.at(i);
                break;
            default:
                break;
            }
        }
        offset = getSdpValue(sdp, "s", tempString, offset);
        ret.s_command = getCommandEnum(tempString);
        offset = getSdpValue(sdp, "c", tempString, offset);
        tempVector = split(tempString, " ");
        for (size_t i = 0; i < tempVector.size(); i++)
        {
            switch (i)
            {
            case 0:
                ret.c_net_type = tempVector.at(i);
                break;
            case 1:
                ret.c_addr_type = tempVector.at(i);
                break;
            case 2:
                ret.c_connection_address = tempVector.at(i);
                break;
            default:
                break;
            }
        }
        offset = getSdpValue(sdp, "t", tempString, offset);
        tempVector = split(tempString, " ");
        for (size_t i = 0; i < tempVector.size(); i++)
        {
            switch (i)
            {
            case 0:
                ret.t_start_time = std::stoull(tempVector.at(i));
                break;
            case 1:
                ret.t_stop_time = std::stoull(tempVector.at(i));
                break;
            default:
                break;
            }
        }
        offset = getSdpValue(sdp, "m", tempString, offset);
        tempVector = split(tempString, " ");
        for (size_t i = 0; i < tempVector.size(); i++)
        {
            switch (i)
            {
            case 0:
                ret.m_media = tempVector.at(i);
                break;
            case 1:
                ret.m_port = std::stoi(tempVector.at(i));
                break;
            case 2:
                ret.m_proto = getProtoEnum(tempVector.at(i));
                break;
            default:
                break;
            }
        }
        offset = getSdpValue(sdp, "y", ret.y_ssrc, offset);
        offset = getSdpValue(sdp, "f", ret.f_media_description, offset);

        if (sdp.find("a=recvonly") != std::string::npos)
            ret.a_mode = GB_SDP_Mode::GB_SDP_Mode_RecvOnly;
        else if (sdp.find("a=sendonly") != std::string::npos)
            ret.a_mode = GB_SDP_Mode::GB_SDP_Mode_SendOnly;
        else
            ret.a_mode = GB_SDP_Mode::GB_SDP_Mode_None;

        if (sdp.find("a=setup:active") != std::string::npos)
            ret.a_tcp_type = GB_SDP_TcpType::GB_SDP_TcpType_Active;
        else if (sdp.find("a=setup:passive") != std::string::npos)
            ret.a_tcp_type = GB_SDP_TcpType::GB_SDP_TcpType_Passive;
        else
            ret.a_tcp_type = GB_SDP_TcpType::GB_SDP_TcpType_None;

        return ret;
    }

    std::string GBSdpFactory::genSdp(GBSdpOptions &options)
    {
        switch (options.a_mode)
        {
        case GB_SDP_Mode::GB_SDP_Mode_SendOnly:
            switch (options.m_proto)
            {
            case GB_SDP_Proto::GB_SDP_Proto_TCP:
                return GBSdpTcpSend().genSdp(options);
            case GB_SDP_Proto::GB_SDP_Proto_UDP:
            default:
                return GBSdpUdpSend().genSdp(options);
                break;
            }
            break;
        case GB_SDP_Mode::GB_SDP_Mode_RecvOnly:
        default:
            switch (options.m_proto)
            {
            case GB_SDP_Proto::GB_SDP_Proto_TCP:
                return GBSdpTcpRecv().genSdp(options);
            case GB_SDP_Proto::GB_SDP_Proto_UDP:
            default:
                return GBSdpUdpRecv().genSdp(options);
                break;
            }
            break;
        }
    }

    std::string GBSdpTcpSend::genSdp(GBSdpOptions &options)
    {
        std::string sdp = "";
        sdp.append(std::string("") + "v=" + options.v_version + std::string("\r\n"));
        sdp.append(std::string("") + "o=" + options.o_username + " " + options.o_sess_id + " " + options.o_sess_version + " " + options.o_net_type + " " + options.o_addr_type + " " + options.o_unicast_address + std::string("\r\n"));
        sdp.append(std::string("") + "s=" + getCommandStr(options.s_command) + std::string("\r\n"));
        if (options.s_command == GB_SDP_Command_Playback || options.s_command == GB_SDP_Command_Download)
        {
            sdp.append(std::string("") + "u=" + options.deviceId + ":0 " + std::string("\r\n"));
            // sdp.append(std::string("") + "u=" + options.u_uri + std::string("\r\n"));
        }
        sdp.append(std::string("") + "c=" + options.c_net_type + " " + options.c_addr_type + " " + options.c_connection_address + std::string("\r\n"));
        sdp.append(std::string("") + "t=" + std::to_string(options.t_start_time) + " " + std::to_string(options.t_stop_time) + std::string("\r\n"));
        sdp.append(std::string("") + "m=" + options.m_media + " " + to_string(options.m_port) + " " + getProtoStr(options.m_proto) + " 96" + std::string("\r\n"));
        sdp.append(std::string("") + "a=sendonly" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:96 PS/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=setup:" + getTcpTypeStr(options.a_tcp_type) + std::string("\r\n"));
        sdp.append(std::string("") + "a=connection:new" + std::string("\r\n"));
        sdp.append(std::string("") + "y=" + options.y_ssrc + std::string("\r\n"));
        if (!options.f_media_description.empty())
            sdp.append(std::string("") + "f=" + options.f_media_description + std::string("\r\n"));
        return sdp;
    };

    std::string GBSdpTcpRecv::genSdp(GBSdpOptions &options)
    {
        std::string sdp = "";
        sdp.append(std::string("") + "v=" + options.v_version + std::string("\r\n"));
        sdp.append(std::string("") + "o=" + options.o_username + " " + options.o_sess_id + " " + options.o_sess_version + " " + options.o_net_type + " " + options.o_addr_type + " " + options.o_unicast_address + std::string("\r\n"));
        sdp.append(std::string("") + "s=" + getCommandStr(options.s_command) + std::string("\r\n"));
        if (options.s_command == GB_SDP_Command_Playback || options.s_command == GB_SDP_Command_Download)
        {
            sdp.append(std::string("") + "u=" + options.deviceId + ":0 " + std::string("\r\n"));
            // sdp.append(std::string("") + "u=" + options.u_uri + std::string("\r\n"));
        }
        sdp.append(std::string("") + "c=" + options.c_net_type + " " + options.c_addr_type + " " + options.c_connection_address + std::string("\r\n"));
        sdp.append(std::string("") + "t=" + std::to_string(options.t_start_time) + " " + std::to_string(options.t_stop_time) + std::string("\r\n"));
        sdp.append(std::string("") + "m=" + options.m_media + " " + to_string(options.m_port) + " " + getProtoStr(options.m_proto) + " 96 97 98" + std::string("\r\n"));
        sdp.append(std::string("") + "a=recvonly" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:96 PS/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:97 MPEG4/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:98 H264/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=setup:" + getTcpTypeStr(options.a_tcp_type) + std::string("\r\n"));
        sdp.append(std::string("") + "a=connection:new" + std::string("\r\n"));
        sdp.append(std::string("") + "y=" + options.y_ssrc + std::string("\r\n"));
        if (!options.f_media_description.empty())
            sdp.append(std::string("") + "f=" + options.f_media_description + std::string("\r\n"));
        return sdp;
    };

    std::string GBSdpUdpSend::genSdp(GBSdpOptions &options)
    {
        std::string sdp = "";
        sdp.append(std::string("") + "v=" + options.v_version + std::string("\r\n"));
        sdp.append(std::string("") + "o=" + options.o_username + " " + options.o_sess_id + " " + options.o_sess_version + " " + options.o_net_type + " " + options.o_addr_type + " " + options.o_unicast_address + std::string("\r\n"));
        sdp.append(std::string("") + "s=" + getCommandStr(options.s_command) + std::string("\r\n"));
        if (options.s_command == GB_SDP_Command_Playback || options.s_command == GB_SDP_Command_Download)
        {
            sdp.append(std::string("") + "u=" + options.deviceId + ":0 " + std::string("\r\n"));
            // sdp.append(std::string("") + "u=" + options.u_uri + std::string("\r\n"));
        }
        sdp.append(std::string("") + "c=" + options.c_net_type + " " + options.c_addr_type + " " + options.c_connection_address + std::string("\r\n"));
        sdp.append(std::string("") + "t=" + std::to_string(options.t_start_time) + " " + std::to_string(options.t_stop_time) + std::string("\r\n"));
        sdp.append(std::string("") + "m=" + options.m_media + " " + to_string(options.m_port) + " " + getProtoStr(options.m_proto) + " 96" + std::string("\r\n"));
        sdp.append(std::string("") + "a=sendonly" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:96 PS/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "y=" + options.y_ssrc + std::string("\r\n"));
        if (!options.f_media_description.empty())
            sdp.append(std::string("") + "f=" + options.f_media_description + std::string("\r\n"));
        return sdp;
    };

    std::string GBSdpUdpRecv::genSdp(GBSdpOptions &options)
    {
        std::string sdp = "";
        sdp.append(std::string("") + "v=" + options.v_version + std::string("\r\n"));
        sdp.append(std::string("") + "o=" + options.o_username + " " + options.o_sess_id + " " + options.o_sess_version + " " + options.o_net_type + " " + options.o_addr_type + " " + options.o_unicast_address + std::string("\r\n"));
        sdp.append(std::string("") + "s=" + getCommandStr(options.s_command) + std::string("\r\n"));
        if (options.s_command == GB_SDP_Command_Playback || options.s_command == GB_SDP_Command_Download)
        {
            sdp.append(std::string("") + "u=" + options.deviceId + ":0 " + std::string("\r\n"));
            // sdp.append(std::string("") + "u=" + options.u_uri + std::string("\r\n"));
        }
        sdp.append(std::string("") + "c=" + options.c_net_type + " " + options.c_addr_type + " " + options.c_connection_address + std::string("\r\n"));
        sdp.append(std::string("") + "t=" + std::to_string(options.t_start_time) + " " + std::to_string(options.t_stop_time) + std::string("\r\n"));
        sdp.append(std::string("") + "m=" + options.m_media + " " + to_string(options.m_port) + " " + getProtoStr(options.m_proto) + " 96 97 98" + std::string("\r\n"));
        sdp.append(std::string("") + "a=recvonly" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:96 PS/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:97 MPEG4/90000" + std::string("\r\n"));
        sdp.append(std::string("") + "a=rtpmap:98 H264/90000" + std::string("\r\n"));
        if (options.s_command == GB_SDP_Command_Download)
            sdp.append(std::string("") + "a=downloadspeed:" + options.a_downloadspeed + std::string("\r\n"));
        if (!options.a_stream.empty())
            sdp.append(std::string("") + "a=stream:" + options.a_stream + std::string("\r\n"));
        if (!options.a_streamMode.empty())
            sdp.append(std::string("") + "a=streamMode:" + options.a_streamMode + std::string("\r\n"));
        if (!options.a_streamprofile.empty())
            sdp.append(std::string("") + "a=streamprofile:" + options.a_streamprofile + std::string("\r\n"));
        if (!options.a_streamnumber.empty())
            sdp.append(std::string("") + "a=streamnumber:" + options.a_streamnumber + std::string("\r\n"));
        sdp.append(std::string("") + "y=" + options.y_ssrc + std::string("\r\n"));
        if (!options.f_media_description.empty())
            sdp.append(std::string("") + "f=" + options.f_media_description + std::string("\r\n"));
        return sdp;
    }
} // namespace STL
